#ifndef XG_ZIPPACKER_H
#define XG_ZIPPACKER_H
///////////////////////////////////////////////////////////
#include "zlib.h"
#include "../stdx/std.h"

static int GZIPCompress(const void* data, u_int32 ndata, void* zdata, u_int32* nzdata)
{
	int err = 0;
	z_stream c_stream;

	if (data && ndata > 0)
	{
		c_stream.zfree = NULL;
		c_stream.zalloc = NULL;
		c_stream.opaque = NULL;

		if (deflateInit2(&c_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK)
		{
			return -1;
		}

		c_stream.avail_in = ndata;
		c_stream.avail_out = *nzdata;
		c_stream.next_in = (Bytef*)(data);
		c_stream.next_out = (Bytef*)(zdata);

		while (c_stream.avail_in && c_stream.total_out < *nzdata)
		{
			if (deflate(&c_stream, Z_NO_FLUSH) != Z_OK) return -1;
		}

		if (c_stream.avail_in) return c_stream.avail_in;

		while (TRUE)
		{
			if ((err = deflate(&c_stream, Z_FINISH)) == Z_STREAM_END) break;

			if (err != Z_OK) return -1;
		}

		if (deflateEnd(&c_stream) != Z_OK) return -1;

		*nzdata = c_stream.total_out;

		return 0;
	}

	return -1;
}

static int GZIPDecompress(const void* zdata, u_int32 nzdata, void* data, u_int32* ndata)
{
	int err = 0;
	z_stream d_stream = { 0 };
	static char dummy_head[2] = { 0x8 + 0x7 * 0x10, (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF };

	d_stream.avail_in = 0;
	d_stream.zfree = NULL;
	d_stream.zalloc = NULL;
	d_stream.opaque = NULL;
	d_stream.next_out = (Bytef*)(data);
	d_stream.next_in = (Bytef*)(zdata);

	if (inflateInit2(&d_stream, MAX_WBITS + 16) != Z_OK) return -1;

	while (d_stream.total_out < *ndata && d_stream.total_in < nzdata)
	{
		d_stream.avail_in = d_stream.avail_out = 1;

		if ((err = inflate(&d_stream, Z_NO_FLUSH)) == Z_STREAM_END) break;

		if (err != Z_OK)
		{
			if (err != Z_DATA_ERROR) return -1;

			d_stream.next_in = (Bytef*)(dummy_head);
			d_stream.avail_in = sizeof(dummy_head);

			if ((err = inflate(&d_stream, Z_NO_FLUSH)) != Z_OK) return -1;
		}
	}

	if (inflateEnd(&d_stream) != Z_OK) return -1;

	*ndata = d_stream.total_out;

	return 0;
}

#ifdef __cplusplus

#include "../stdx/std.h"

typedef struct
{
	u_int32 sz;
	u_int32 dx;
	u_int8 data[1];
} ZipPackerNode;

class ZipPacker : public Object
{
public:
	const static u_int32 NODE_MINSZ = 24;
	const static u_int32 NODE_MAXSZ = XG_MEMFILE_MAXSZ;

public:
	static void DestroyNode(ZipPackerNode* node)
	{
		if (node) free(node);
	}
	static SmartBuffer Unpack(const ZipPackerNode* node)
	{
		assert(node->sz > 0 && node->sz <= NODE_MAXSZ);

		SmartBuffer buffer(node->sz - sizeof(u_int32) - sizeof(u_int32) + node->dx);

		if (node->dx == 0)
		{
			memcpy(buffer.str(), node->data, buffer.size());
		}
		else
		{
			uLongf len = buffer.size();

			if (uncompress((Bytef*)(buffer.str()), &len, node->data, len - node->dx))
			{
				buffer.free();
				
				return buffer;
			}
		}

		return buffer;
	}
	static int Unpack(const ZipPackerNode* node, void* dest, u_int32 destsz)
	{
		assert(node->sz > 0 && node->sz <= NODE_MAXSZ);

		uLongf sz = node->sz - sizeof(u_int32) - sizeof(u_int32);

		if (node->dx == 0)
		{
			memcpy(dest, node->data, sz);

			return sz;
		}
		else
		{
			uLongf len = destsz;

			if (uncompress((Bytef*)(dest), &len, node->data, node->sz - sizeof(u_int32) - sizeof(u_int32)))
			{
				return XG_ERROR;
			}

			return len;
		}
	}
	static ZipPackerNode* Pack(const void* data, u_int32 sz)
	{
		assert(sz <= NODE_MAXSZ);

		uLongf len = 0;
		ZipPackerNode* ptr = NULL;

		if (sz < NODE_MINSZ)
		{
			len = sz + sizeof(u_int32) + sizeof(u_int32);
			ptr = (ZipPackerNode*)malloc(len);
	
			if (NULL == ptr) return NULL;

			memcpy(ptr->data, data, sz);
			ptr->sz = (u_int32)(len);
			ptr->dx = 0;
		}
		else
		{
			len = compressBound(sz);

			if (len <= 0) return NULL;

			ptr = (ZipPackerNode*)malloc(len + sizeof(u_int32) + sizeof(u_int32));
			
			if (ptr == NULL) return NULL;

			if (compress((Bytef*)(ptr->data), &len, (Bytef*)(data), (uLong)(sz))) return NULL;

			ptr->sz = (u_int32)(len) + sizeof(u_int32) + sizeof(u_int32);
			ptr->dx = (u_int32)(sz - len);

			if (sz <= len)
			{
				memcpy(ptr->data, data, sz);
				ptr->sz = sz + sizeof(u_int32) + sizeof(u_int32);
				ptr->dx = 0;
			}
		}

		return ptr;
	}
	static ZipPackerNode* Pack(const void* src, u_int32 srcsz, void* dest, u_int32 destsz)
	{
		assert(srcsz <= NODE_MAXSZ);

		uLongf len = 0;
		ZipPackerNode* ptr = (ZipPackerNode*)(dest);;

		if (srcsz < NODE_MINSZ)
		{
			len = srcsz + sizeof(u_int32) + sizeof(u_int32);
			memcpy(ptr->data, src, srcsz);
			ptr->sz = (u_int32)(len);
			ptr->dx = 0;
		}
		else
		{
			len = destsz - sizeof(u_int32) - sizeof(u_int32);

			if (compress((Bytef*)(ptr->data), &len, (Bytef*)(src), (uLong)(srcsz))) return NULL;

			ptr->sz = (u_int32)(len) + sizeof(u_int32) + sizeof(u_int32);
			ptr->dx = (u_int32)(srcsz - len);

			if (srcsz <= len)
			{
				memcpy(ptr->data, src, srcsz);
				ptr->sz = srcsz + sizeof(u_int32) + sizeof(u_int32);
				ptr->dx = 0;
			}
		}

		return ptr;
	}
};
#endif

///////////////////////////////////////////////////////////
#endif


